(function($) {

var methods = {
		init : function( options ) {	
			
			$this = this;
			
			var rtcObject = new RTCVideo(options.initiator, options.roomKey, options.me, options.channelId, $this);
			$.data($this, "rtcObject", rtcObject);	
	
		}
}
	
// Init function
$.fn.cis = function(method) {
	// Method calling logic
    if ( methods[method] ) {
      return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
    }   
}

$.fn.cis.default_options = {
		create_url : "create",
		open_url   : "open"
}
	
function RTCVideo(initiator, roomKey, me, channelId, card) {
	// Initializing object attributes
	this.localVideo          = null;
	this.miniVideo           = null;
	this.remoteVideo         = null;
	this.localStream         = null;
	this.channel             = null;
	this.channelReady        = false;
	this.pc                  = null;
	this.socket              = null;
	this.initiator           = initiator;
	this.started             = false;
	this.iceUfrags           = [];
	this.icePwds             = [];
	this.needFormatCandidate = false;
	this.roomKey             = roomKey;
	this.me 			     = me;
	this.card                = card;
	this.channelId           = channelId;
	
	this.initialize();
}

RTCVideo.prototype = {
		initialize : function() {
			
			$this = this;
			
			console.log("Initializing; room = " + $this.roomKey);
			
			$this.localVideo  = $("<video/>", {id:"localVideo",  autoplay:"autoplay"});
			$this.miniVideo   = $("<video/>", {id:"miniVideo",   autoplay:"autoplay"});
			$this.remoteVideo = $("<video/>", {id:"remoteVideo", autoplay:"autoplay"});
			$this.footer      = $("<div/>",   {id:"footer"});
			
			$this.card.append($this.localVideo);
			$this.card.append($this.miniVideo);
			$this.card.append($this.remoteVideo);
			$this.card.append($this.footer);
			
			$this.resetStatus();
			$this.openChannel();
			$this.getUserMedia();
		},
		
		setStatus : function(state) {
			this.footer.html(state);
		},
		
		resetStatus : function() {
			$this = this;
			if (!$this.initiator) {
			   // ROOM link
			   $this.setStatus("Waiting for someone to join: <a href=\"TODO\">TODO</a>");
			} else {
			   $this.setStatus("Initializing...");
			}
		},		
		
		openChannel : function() {
			console.log("Opening channel.");
			
			$this = this;
			
			$this.channel = new Channel("../channelpool.php", $this.channelId, { onServerMessage: function(data) { $this.onChannelMessage(data) }  });
			
			/*
			 * var handler = { 'onopen' : onChannelOpened, 'onmessage' :
			 * onChannelMessage, 'onerror' : onChannelError, 'onclose' :
			 * onChannelClosed };
			 */
			
			// socket = channel.open(handler);
		},
		
		getUserMedia : function() {
			
			$this = this;
			
			try {
			   navigator.webkitGetUserMedia({audio:true, video:true}, function(){$this.onUserMediaSuccess()},
			                                 function(){$this.onUserMediaError});
			   console.log("Requested access to local media with new syntax.");
			} catch (e) {
				try {
					navigator.webkitGetUserMedia("video,audio", function(stream){$this.onUserMediaSuccess(stream)},
			                                 function(){$this.onUserMediaError});
					console.log("Requested access to local media with old syntax.");
			    } catch (e) {
			      alert("webkitGetUserMedia() failed. Is the MediaStream flag enabled in about:flags?");
			      console.log("webkitGetUserMedia failed with exception: " + e.message);
			    }
			}
		},
		
		onUserMediaSuccess : function(stream) {
			console.log("User has granted access to local media.");
			$this = this;
			
			var url = webkitURL.createObjectURL(stream);
			$this.localVideo.css("opacity", 1);
			($this.localVideo)[0].src = url;
			$this.localStream = stream;
			
			// Caller creates PeerConnection.
			if ($this.initiator) $this.maybeStart();
		},
		
		onUserMediaError : function(error) {
			console.log("Failed to get access to local media. Error code was " + error.code);
			alert("Failed to get access to local media. Error code was " + error.code + ".");
		},
		
		maybeStart : function() {
			$this = this;
			
			if (!$this.started && $this.localStream && $this.channelReady) {
				$this.setStatus("Connecting...");
			    console.log("Creating PeerConnection.");
			    $this.createPeerConnection();
			    console.log("Adding local stream.");
			    $this.pc.addStream(localStream);
			    $this.started = true;
			    // Caller initiates offer to peer.
			    if ($this.initiator) $this.doCall();
			}
		},
		
		createPeerConnection : function() {
			
			$this = this;
			
			try {
			   //TODO pc_config
			   $this.pc = new webkitPeerConnection00("{{ pc_config }}", function(){ $this.onIceCandidate();});
			   console.log("Created webkitPeerConnnection00 with config \"{{ pc_config }}\".");
			} catch (e) {
			   console.log("Failed to create PeerConnection, exception: " + e.message);
			   alert("Cannot create PeerConnection object; Is the 'PeerConnection' flag enabled in about:flags?");
			   return;
			}
			
			$this.pc.onconnecting   = function() { $this.onSessionConnecting() };
			$this.pc.onopen         = function() { $this.onSessionOpened() };
			$this.pc.onaddstream    = function() { $this.onRemoteStreamAdded() };
			$this.pc.onremovestream = function() { $this.onRemoteStreamRemoved() };
		},
		
		doCall : function() {
			$this = this;
			console.log("Send offer to peer");
			
			var offer = $this.pc.createOffer({audio:true, video:true});
			$this.pc.setLocalDescription($this.pc.SDP_OFFER, offer);
			$this.sendMessage({type: 'offer', sdp: offer.toSdp()});
			$this.pc.startIce();
		},
		
		doAnswer : function() {
			$this = this;
			
			console.log("Send answer to peer");
			var offer = $this.pc.remoteDescription;
			var answer = $this.pc.createAnswer(offer.toSdp(), {audio:true,video:true});
			$this.pc.setLocalDescription($this.pc.SDP_ANSWER, answer);
			$this.sendMessage({type: 'answer', sdp: answer.toSdp()});
			$this.pc.startIce();
		},
		
		sendMessage : function(message) {
			$this = this;
			
			var msgString = JSON.stringify(message);
			console.log('C->S: ' + msgString);
			
			//TODO make the url a parameter.
			$.post('message.php', {room : $this.roomKey, user : $this.me, msg : message});
		},
		
		processSignalingMessage : function(message) {
			
			$this = this;
			
			var msg = JSON.parse(message);
	
			if (msg.type === 'offer') {
			  // Callee creates PeerConnection
			  if (!$this.initiator && !$this.started) $this.maybeStart();
			   
			  $this.pc.setRemoteDescription($this.pc.SDP_OFFER, new SessionDescription(msg.sdp));
			  $this.checkIceFormat(msg.sdp);
			  $this.doAnswer();
			} else if (msg.type === 'answer' && $this.started) {
			  $this.pc.setRemoteDescription($this.pc.SDP_ANSWER, new SessionDescription(msg.sdp));
			  $this.checkIceFormat(msg.sdp);
			} else if (msg.type === 'candidate' && $this.started) {
			  var candidateString = $this.maybeAddIceCredentials(msg);
			  var candidate = new IceCandidate(msg.label, candidateString);
			  $this.pc.processIceMessage(candidate);
			} else if (msg.type === 'bye' && $this.started) {
			  $this.onRemoteHangup();
			}
		},
		
		// Temp solution for compatibility between Chrome 20 and later versions.
		// We need to convert the ICE candidate into old format at Chrome 20 end.
		checkIceFormat : function(msgString) {
		  var ua = navigator.userAgent;
		  if (ua.substr(ua.lastIndexOf('Chrome/')+7, 2) === '20') {
		    // If the offer/answer is from later Chrome to Chrome 20
		    // Save the username and password of both audio and video
		    if (msgString.search('ice-ufrag:') !== -1 &&
		          msgString.search('ice-pwd:') !== -1) {
		      $this.saveIceCredentials(msgString);
		      $this.needFormatCandidate = true;
		    }
		  }
		},
		
		// Save the ICE credentials in SDP from later Chrome at Chrome 20 end.
		saveIceCredentials : function(msgString) {
			
			$this = this;
			
			var indexOfAudioSdp = msgString.search('m=audio');
			var indexOfVideoSdp = msgString.search('m=video');

			// Candidate label 0 for audio, 1 for video
			var audioSdp = msgString.substring(indexOfAudioSdp, indexOfVideoSdp);
			$this.iceUfrags[0] = audioSdp.substr(audioSdp.search('ice-ufrag:')+10, 16);
			$this.icePwds[0] = audioSdp.substr(audioSdp.search('ice-pwd:')+8, 24);
			
			var videoSdp = msgString.substring(indexOfVideoSdp);
			$this.iceUfrags[1] = videoSdp.substr(videoSdp.search('ice-ufrag:')+10, 16);
		 	$thisicePwds[1] = videoSdp.substr(videoSdp.search('ice-pwd:')+8, 24);
		},
		
		maybeAddIceCredentials : function(msg) {
			
			$this = this;
			
			var candidateString = msg.candidate;
		  
			if ($this.needFormatCandidate) {
				candidateString = msg.candidate.replace('generation',
		                                                'username ' + $this.iceUfrags[msg.label] +
		                                                ' password ' + $this.icePwds[msg.label] +
		                                                ' generation');
			}
			
			return candidateString;
		},
		
		//TODO implement it in the channel protocol
		onChannelOpened : function() {
			$this = this;
			console.log('Channel opened.');
			
			$this.channelReady = true;
			
			if ($this.initiator) $this.maybeStart();
		},
		
		onChannelMessage : function(message) {
			$this = this;
			
			console.log('S->C: ' + message.data);
			$this.processSignalingMessage(message.data);
		},
		
		onChannelError : function() {
		  console.log('Channel error.');
		},
		
		onChannelClosed : function() {
		  console.log('Channel closed.');
		},
		
		onIceCandidate : function(candidate, moreToFollow) {
		  
			$this = this;
			
			if (candidate) {
			  $this.sendMessage({type: 'candidate',
			               label: candidate.label, candidate: candidate.toSdp()});
			}

			if (!moreToFollow) {
				console.log("End of candidates.");
			}
		},

		onSessionConnecting : function(message) {
			console.log("Session connecting.");
		},
		
		onSessionOpened : function(message) {
			console.log("Session opened.");
		},
		
		onRemoteStreamAdded : function(event) {
			
			$this = this;
			
			console.log("Remote stream added.");
			var url = webkitURL.createObjectURL(event.stream);
			$this.miniVideo.src = $this.localVideo.src;
			$this.remoteVideo.src = url;
			$this.waitForRemoteVideo();  
		},
		
		onRemoteStreamRemoved : function(event) {
		    console.log("Remote stream removed.");
		},
		
		onHangup : function() {
			
			$this = this;
			
			console.log("Hanging up.");
			$this.transitionToDone();
			$this.stop();
		  
			// will trigger BYE from server
			//TODO close channel
		},
		
	    onRemoteHangup : function() {
			
	    	$this = this;
	    	
	    	console.log('Session terminated.');
			$this.transitionToWaiting();
			$this.stop();
		  	$this.initiator = 0;
		},
		
		stop : function() {
			$this = this;
			$this.tarted = false;
			$this.needFormatCandidate = false;
			$this.pc.close();
			$this.pc = null;
		},
		
		waitForRemoteVideo : function() {
			$this = this;
			
			if ($this.remoteVideo.currentTime > 0) {
			   $this.transitionToActive();
			} else {
			   setTimeout(function() { $this.waitForRemoteVideo() }, 100);
		    }
		},
		
		transitionToActive : function() {
			$this = this;
			$this.remoteVideo.style.opacity = 1;
			$this.card.style.webkitTransform = "rotateY(180deg)";
			setTimeout(function() { localVideo.src = ""; }, 500);
			setTimeout(function() { miniVideo.style.opacity = 1; }, 1000);
			$this.setStatus("<input type=\"button\" id=\"hangup\" value=\"Hang up\" onclick=\"onHangup()\" />");
		},
		
		transitionToWaiting : function() {
			$this = this;
			
			$this.card.style.webkitTransform = "rotateY(0deg)";
			setTimeout(function() { $this.localVideo.src = $this.miniVideo.src; $this.miniVideo.src = ""; $this.remoteVideo.src = "" }, 500);
			$this.miniVideo.css("opacity", 0);
			$this.remoteVideo.css("opacity", 0);
			$this.resetStatus();
		},
		
		transitionToDone : function() {
			$this = this;
			
			$this.localVideo.css("opacity", 0);
			$this.remoteVideo.css("opacity", 0);
			$this.miniVideo.css("opacity", 0);
			$this.setStatus("You have left the call. <a href=\"{{ room_link }}\">Click here</a> to rejoin.");
		},
		
		enterFullScreen : function() {
		    remote.webkitRequestFullScreen();
		}
		
}
	
})(jQuery);